package haw.pr2.A3.implementations;

import static com.google.common.base.Preconditions.*;
import java.util.ArrayList;
import java.util.Collection;
import static haw.pr2.A3.implementations.UtilityclassValues.*;
import haw.pr2.A3.interfaces.*;
import java.util.HashSet;
import java.util.Set;

/**
 *
 * @author Fabian Heidorn, Alexander Kaack
 */
class Bounded3DimStackImpl<E extends WithStowLoc> implements Bounded3DimStack<E> {

    /* 
     * Implementation durch 3-fach geschachtelte ArrayLists
     * Verwendung der List Methoden
     * Delegation und Iteration mit etwas drumherum
     * 
     */
    ArrayList<ArrayList<ArrayList<E>>> stack;
    int bay, row, tier;
    E nullElem;

    private Bounded3DimStackImpl(int bay, int row, int tier, Object nullElem) {
        this.bay = bay;
        this.row = row;
        this.tier = tier;
        this.nullElem = (E) nullElem;

        //stack initialisieren
        stack = new ArrayList<ArrayList<ArrayList<E>>>(bay); //bay
        for (int i = 0; i < bay; i++) {
            stack.add(new ArrayList<ArrayList<E>>(row));//row
            for (int j = 0; j < row; j++) {
                stack.get(i).add(new ArrayList<E>(tier));//tier
                for (int k = 0; k < tier; k++) {
                    stack.get(i).get(j).add(this.nullElem);//tier
                }
            }
        }
    }

    public static Bounded3DimStack valueOf(int bay, int row, int tier, Object nullElem) {
        return new Bounded3DimStackImpl(bay, row, tier, nullElem);
    }

    @Override
    public void load(int bayNo, int rowNo, E elem) {
       checkArgument(!this.tierIsFull(bayNo, rowNo));
        for (int i = 0; i < stack.get(bayNo).get(rowNo).size(); i++) {
            if (stack.get(bayNo).get(rowNo).get(i).equals(this.nullElem)) {
                stack.get(bayNo).get(rowNo).set(i, elem);
            }
        }
    }

    @Override
    public void load(E elem) {
        checkArgument(!this.isFull());
        int bayIndex = 0; 
        int rowIndex = 0;
        int tierIndex = 0;
        boolean end = false;
        while (bayIndex < bay && !end) {
            rowIndex=0;
            while (rowIndex < row && !end) {
                tierIndex=0;
                while (tierIndex < tier && !end) {
                    if (!this.tierIsFull(bayIndex, rowIndex)) {
                        this.load(bayIndex, rowIndex, elem);
                        end = true;
                    }
                    tierIndex++;
                }
                rowIndex++;
            }
            bayIndex++;
        }
    }

    @Override
    public void loadAll(Collection<E> coll) {
        for (E e : coll) {
                 this.load(e);
        }
    }

    @Override
    public boolean isEmpty() {
        for (ArrayList<ArrayList<E>> bayList : stack) {
            for (ArrayList<E> rowList : bayList) {
                for (E e : rowList) {
                    if (!e.equals(nullElem)) {
                        return false;
                    }
                }
            }
        }
        return true;
    }

    @Override
    public boolean isFull() {
        for (ArrayList<ArrayList<E>> bayList : stack) {
            for (ArrayList<E> rowList : bayList) {
                for (E e : rowList) {
                    if (e.equals(nullElem)) {
                        return false;
                    }
                }
            }
        }
        return true;
    }

    @Override
    public boolean tierIsEmpty(int bay, int row) {
        checkArgument(bay <= stack.size() && row <= stack.get(bay).size());
        for (E elem : stack.get(bay).get(row)) {
            if (!elem.equals(nullElem)) {
                return false;
            }
        }
        return true;
    }

    @Override
    public boolean tierIsFull(int bay, int row) {
        checkArgument(bay <= stack.size() && row <= stack.get(bay).size());
        for (E elem : stack.get(bay).get(row)) {
            if (elem.equals(nullElem)) {
                return false;
            }
        }
        return true;
    }

    @Override
    public boolean contains(Object elem) {
        return this.getAll().contains((E) elem);
    }

    @Override
    public boolean containsAll(Collection<?> coll) {
        return this.getAll().containsAll(coll);
    }

    @Override
    public E get(StowageLocation loc) {
        checkArgument(loc.bay() <= stack.size() && loc.row() <= stack.get(bay).size() && loc.tier() <=stack.get(bay).get(row).size());
        return stack.get(loc.bay()).get(loc.row()).get(loc.tier());
    }

    @Override
    public Set<E> getAll() {
        Set res = new HashSet();
        for (ArrayList<ArrayList<E>> bayList : stack) {
            for (ArrayList<E> rowList : bayList) {
                    res.addAll(rowList);

            }
        }
        return res;
    }

    @Override
    public StowageLocation locationOf(E elem) {
        checkArgument(this.contains(elem));
        for (int bayIndex = 0; bayIndex < bay; bayIndex++) {
            for (int rowIndex = 0; rowIndex < row; rowIndex++) {
                for (int tierIndex = 0; tierIndex < tier; tierIndex++) {
                    if (stack.get(bayIndex).get(rowIndex).get(tierIndex) == elem) {
                        return stowageLocation(bayIndex, rowIndex, tierIndex);
                    }
                }
            }
        }
        return nullLocation();
    }
}
